/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file -- DO NOT EDIT!

template <Format format, typename It, typename... Types>
static size_t Emit(It out, Types... args) { // NOLINT(readability-function-size)
    size_t insn_len = 0;

% insns_uniq_sort_fmts.each do |i| # Panda::formats.each do |fmt|
%   fmt = i.format
%   ops = i.operands
%   offsets = [0]  # for opcode
%   offsets += ops.map(&:offset).sort
%   offsets += [fmt.size * 8]  # terminating offset, used for calculating last operand encoding width
%
    // Disable check due to clang-tidy bug https://bugs.llvm.org/show_bug.cgi?id=32203
    // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
    if constexpr (format == Format::<%= fmt.pretty.upcase %>) {
        constexpr size_t SIZE = <%= fmt.size %>;
        constexpr std::array<uint8_t, <%= offsets.size %>> OFF{<%= offsets.join(', ') %>};
        static_assert(OFF.size() == sizeof...(args) + 1);
        std::array<uint8_t, SIZE> buf{};
        Span<uint8_t> buf_sp(buf);
        Span<const uint8_t> off_sp(OFF);
        EmitImpl(buf_sp, off_sp, args...);
        std::copy(buf.begin(), buf.end(), out);
        insn_len = SIZE;
    }

% end
    return insn_len;
}

% OPCODE_TYPE = 'BytecodeInstruction::Opcode'
% FORMAT_TYPE = 'BytecodeInstruction::Format'
%
% def opcode_full_name(i)
%   OPCODE_TYPE + '::' + i.opcode.upcase
% end
%
% def format_full_name(i)
%   FORMAT_TYPE + '::' + i.format.pretty.upcase
% end
%
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
%   emitter_name = group.first.emitter_name
%   formats = group.map(&:format)
%   signature = emitter_signature(group, group.first.jump?)
%   signature_str = signature.map { |o| "#{o.type} #{o.name}" }.join(', ')
%
// NOLINTNEXTLINE(misc-definitions-in-headers)
void BytecodeEmitter::<%= emitter_name %>(<%= signature_str %>) {
%
%   method_args = signature.map(&:name)
%   opcodes = group.map { |i| opcode_full_name(i) }
%
%   i = group[0]
%
%   if i.jump?
%       jmp_args = [opcode_full_name(i)] + method_args[0..-2] + ["0"]
%       format = format_full_name(i)
    branches_.insert(std::make_pair(pc_, label));
    pc_ += Emit<<%= format %>>(std::back_inserter(bytecode_), <%= jmp_args.join(', ') %>);
%   elsif group.length() == 1
%       jmp_args = [opcode_full_name(i)] + method_args
%       format = format_full_name(i)
    pc_ += Emit<<%= format %>>(std::back_inserter(bytecode_), <%= jmp_args.join(', ') %>);
%   else
%       bitlen_vars = []
%       signature.each do |arg|
%           v = '%s_bitlen' % arg.name
%           bitlen_vars.push(v)
%           if arg.type.start_with?('u')
    auto <%= v %> = GetBitLengthUnsigned(<%= arg.name %>);
%           else
    auto <%= v %> = GetBitLengthSigned(<%= arg.name %>);
%           end
%       end
%
%       group.each do |i|
%           conditions = []
%           i.operands.each_with_index do |op, index|
%               conditions.push("%s <= BitImmSize::BITSIZE_%d" % [bitlen_vars[index], op.width])
%           end
%           format = format_full_name(i)
%           opcode = opcode_full_name(i)

    if (<%= conditions.join(' && ') %>) {
        pc_ += Emit<<%= format %>>(std::back_inserter(bytecode_), <%= opcode %>, <%= method_args.join(', ') %>);
        return;
    }
%       end
%   end
}

% end

/* static */
// NOLINTNEXTLINE(readability-function-size,misc-definitions-in-headers)
size_t BytecodeEmitter::GetSizeByOpcode(<%= OPCODE_TYPE %> opcode) {
    switch (opcode) {
% Panda::instructions.each do |insn|
    case <%= opcode_full_name(insn) %>:
       return <%= insn.format.size %>;  // NOLINT(readability-magic-numbers)
% end
    default:
        UNREACHABLE();
        return 0;
    }
}

template <Format format, typename OffsT>
static void UpdateBranchByFormat(uint8_t *insn, OffsT offs) { // NOLINT(readability-function-size)
% uniq_fmt_jmps = Panda.instructions.select { |i| i.jump? }.uniq { |i| i.format.pretty }.sort_by { |insn| insn.format.pretty }
% uniq_fmt_jmps.each do |i|
%   fmt = i.format
%   offsets = [i.operands.map(&:offset).max, fmt.size * 8]
%
    // Disable check due to clang-tidy bug https://bugs.llvm.org/show_bug.cgi?id=32203
    // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
    if constexpr (format == Format::<%= fmt.pretty.upcase %>) {
        constexpr size_t SIZE = <%= fmt.size %>;
        constexpr std::array<uint8_t, <%= offsets.size %>> OFF{<%= offsets.join(', ') %>};
        Span<const uint8_t> off_sp(OFF);
        Span<uint8_t> insn_sp(insn, SIZE);
        EmitImpl(insn_sp, off_sp, offs);
        return;
    }

% end
    UNREACHABLE();
}

/* static */
// NOLINTNEXTLINE(misc-definitions-in-headers)
void BytecodeEmitter::UpdateBranchOffs(uint8_t *insn, int32_t offs) {
    auto opcode = BytecodeInstruction(insn).GetOpcode();
    switch (opcode) {
% Panda::instructions.each do |insn|
%   if insn.jump?
    case <%= opcode_full_name(insn) %>:
        UpdateBranchByFormat<<%= format_full_name(insn) %>>(insn, offs);
        return;
%   end
% end
    default:
        UNREACHABLE();
    }
}

/* static */
// NOLINTNEXTLINE(misc-definitions-in-headers)
BytecodeEmitter::BitImmSize BytecodeEmitter::GetBitImmSizeByOpcode(<%= OPCODE_TYPE %> opcode) {
    switch (opcode) {
% Panda::instructions.each do |insn|
%   if insn.jump?
    case <%= opcode_full_name(insn) %>:
        return BytecodeEmitter::BitImmSize::BITSIZE_<%= insn.operands.select(&:imm?).first.width %>;
%   end
% end
    default:
        UNREACHABLE();
        return BytecodeEmitter::BitImmSize::BITSIZE_32;  // Any return value will do, we are broken here anyway.
    }
}

/* static */
// NOLINTNEXTLINE(misc-definitions-in-headers)
<%= OPCODE_TYPE %> BytecodeEmitter::RevertConditionCode(<%= OPCODE_TYPE %> opcode) {
% CC_INVERTER = {
%       "JEQ" => "JNE",
%       "JNE" => "JEQ",
%       "JLT" => "JGE",
%       "JGE" => "JLT",
%       "JGT" => "JLE",
%       "JLE" => "JGT",
% }
    switch (opcode) {
% Panda::instructions.each do |insn|
%   if insn.conditional?
%     pref = insn.format.prefixed? ? insn.prefix.name.upcase + "_" : ""
%     op = insn.opcode.upcase.delete_prefix(pref)
%     op = CC_INVERTER[/^.../.match(op).to_s] ? op.sub(/^.../, CC_INVERTER).prepend(pref) : "LAST"
    case <%= opcode_full_name(insn) %>:
        return <%= OPCODE_TYPE %>::<%= op %>;
%   end
% end
    default:
        UNREACHABLE();
        return <%= OPCODE_TYPE %>::LAST;  // Any return value will do, we are broken here anyway.
    }
}

/* static */
// NOLINTNEXTLINE(misc-definitions-in-headers)
<%= OPCODE_TYPE %> BytecodeEmitter::GetLongestJump(<%= OPCODE_TYPE %> opcode) {
    switch (opcode) {
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
%   insn = group[0]
%   if insn.jump?
%     group.each do |i|
    case <%= OPCODE_TYPE %>::<%= i.opcode.upcase %>:
%     end
        return <%= OPCODE_TYPE %>::<%= group[-1].opcode.upcase %>;
%   end
% end
    default:
        UNREACHABLE();
        return <%= OPCODE_TYPE %>::LAST;  // Any return value will do, we are broken here anyway.
    }
}

/* static */
// // NOLINTNEXTLINE(readability-function-size,misc-definitions-in-headers)
<%= OPCODE_TYPE %> BytecodeEmitter::GetSuitableJump(<%= OPCODE_TYPE %> opcode, BytecodeEmitter::BitImmSize width) {
    switch (opcode) {
% jmp_w_list = [32, 16, 8, 4]
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
%   insn = group[0]
%   if insn.jump?
    case <%= OPCODE_TYPE %>::<%= insn.opcode.upcase %>:
        switch (width) {
%     suit_ind = 0
%     jmp_w_list.each do |w|
%       if group[suit_ind - 1] != nil && w == group[suit_ind - 1].operands.select(&:imm?).first.width
%         if w != jmp_w_list[0]
            return <%= OPCODE_TYPE %>::<%= (suit_ind == 0) ? "LAST" : group[suit_ind].opcode.upcase %>;
%         end
%         suit_ind = suit_ind - 1
%       end
        case BytecodeEmitter::BitImmSize::BITSIZE_<%= w %>:
%     end
            return <%= OPCODE_TYPE %>::<%= (suit_ind == 0) ? "LAST" : group[suit_ind].opcode.upcase %>;
        }
        break;
%   end
% end
    default:
        UNREACHABLE();
        return <%= OPCODE_TYPE %>::LAST;  // Any return value will do, we are broken here anyway.
    }
    UNREACHABLE();
    return <%= OPCODE_TYPE %>::LAST;  // Any return value will do, we are broken here anyway.
}
